using System;
using System.Collections.Generic;
using UnityEngine;

namespace UnityExt.Events
{
    
    public class UnitySignals: IDisposable
    {
        public static UnitySignals Default = new UnitySignals();
        readonly Dictionary<int, List<ISignalRecieve>> _signals = new Dictionary<int, List<ISignalRecieve>>(new EqualityComparer());

        public void On(ISignalRecieve recieve, Type type)
        {
            List<ISignalRecieve> cachedSignals;
            if (_signals.TryGetValue(type.GetHashCode(), out cachedSignals))
            {
                cachedSignals.Add(recieve);
            }
            else
            {
                _signals.Add(type.GetHashCode(), new List<ISignalRecieve> {recieve});
            }
        }

        public void Off(ISignalRecieve recieve, Type type)
        {
            List<ISignalRecieve> cachedSignals;
            if (_signals.TryGetValue(type.GetHashCode(), out cachedSignals))
            {
                cachedSignals.Remove(recieve);
            }
        }


        public void On(object obj)
        {
            var all = obj.GetType().GetInterfaces();
            var reciever = obj as ISignalRecieve;
            foreach (var intType in all)
            {
                if (intType.IsGenericType && intType.GetGenericTypeDefinition() == typeof(ISignalRecieve<>))
                {
                    On(reciever, intType.GetGenericArguments()[0]);
                }
            }
        }

        public void Off(object obj)
        {
            var all = obj.GetType().GetInterfaces();
            var reciever = obj as ISignalRecieve;
            foreach (var intType in all)
            {
                if (intType.IsGenericType && intType.GetGenericTypeDefinition() == typeof(ISignalRecieve<>))
                {
                    Off(reciever, intType.GetGenericArguments()[0]);
                }
            }
        }

        public void Dispatch<T>(T val)
        {
            List<ISignalRecieve> cachedSignals;
            if (_signals.TryGetValue(typeof(T).GetHashCode(), out cachedSignals))
            {
                var len = cachedSignals.Count;
                for (var i = 0; i < len; i++)
                {
                    (cachedSignals[i] as ISignalRecieve<T>).OnReceived(val);
                }
            }
        }

        public void Dispose()
        {
            _signals.Clear();
        }

        public class EqualityComparer : IEqualityComparer<int>
        {
            public bool Equals(int x, int y)
            {
                return x == y;
            }

            public int GetHashCode(int obj)
            {
                return obj.GetHashCode();
            }
        }
    }
    public interface ISignalRecieve<T> : ISignalRecieve
    {
        void OnReceived(T arg);
    }
    public interface ISignalRecieve
    {
    }

    public abstract class SignaledBehaviour<T> : MonoBehaviour, ISignalRecieve<T>
    {
        public virtual void OnReceived(T arg){}

        public void Awake()
        {
            UnitySignals.Default?.On(this);
        }
        public void OnDestroy()
        {
            UnitySignals.Default?.Off(this);
        }
    }
    public static class SignalBehaviourExt
    {
        public static void Dispatch<T>(this MonoBehaviour behaviour,T obj)
        {
            UnitySignals.Default?.Dispatch(obj);
        }
        public static void On(this MonoBehaviour behaviour)
        {
            UnitySignals.Default?.On(behaviour);
        }
        public static void Off(this MonoBehaviour behaviour)
        {
            UnitySignals.Default?.Off(behaviour);
        }
    }
}
